using System;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Web;
using System.Web.Compilation;
using System.Web.Configuration;

namespace PostSharp.AspNet
{
    /// <summary>
    /// Hooks into the ASP.NET compilation process by post-processing
    /// assemblies using PostSharp (implementation of <see cref="IAssemblyPostProcessor"/>).
    /// </summary>
    /// <remarks>
    /// <para>In order to use PostSharp in a web project, specify this class
    /// as an assembly post-processor in <b>web.config</b>:</para>
    /// <code>
    /// &lt;configuration&gt;
    ///      &lt;system.web&gt;
    ///        &lt;compilation debug="true" assemblyPostProcessorType="PostSharp.AspNet.AssemblyPostProcessor, PostSharp.AspNet"/&gt;
    ///      &lt;/system.web&gt;
    /// &lt;/configuration&gt;
    /// </code>
    /// </remarks>
    /// <seealso cref="PostSharpConfiguration"/>
    public class AssemblyPostProcessor : IAssemblyPostProcessor
    {
    

        /// <summary>
        /// Initializes a new <see cref="AssemblyPostProcessor"/>.
        /// </summary>
        public AssemblyPostProcessor()
        {
          
        }

        /// <summary>
        /// Method invoked after the C# or VB compiler has done its job.
        /// </summary>
        /// <param name="path">Full path to the newly created assembly.</param>
        public void PostProcessAssembly( string path )
        {
            try
            {
                // If the C# compiler fails, we get an inexisting path here. But we should
                // not throw an exception, otherwise the user will not see C# error messages.
                if ( !File.Exists( path ) )
                    return;

                string project;

                PostSharpConfiguration configuration = WebConfigurationManager.GetSection("postsharp") as PostSharpConfiguration ?? new PostSharpConfiguration();

                if (string.IsNullOrEmpty(HttpRuntime.AppDomainAppPath))
                    throw new InvalidOperationException("Cannot invoke PostSharp.AspNet outside of an ASP.NET context.");


                string applicationPath = HttpRuntime.AppDomainAppPath;
                string directory = configuration.Directory;
                if (string.IsNullOrEmpty(directory))
                    directory = "%POSTSHARP20%\\Release";
                directory = directory.Replace("~", applicationPath);
                string postsharpPath = Environment.ExpandEnvironmentVariables(directory);

                if (string.IsNullOrEmpty(configuration.Project))
                    project = Path.Combine(postsharpPath, "Default.psproj");
                else
                    project = Path.Combine(applicationPath, configuration.Project);

                // Find out which program we should run.
                string program = configuration.Program;
                if ( string.IsNullOrEmpty( program ))
                {
                    string processor = IntPtr.Size == 4 ? "x86" : "x64";
                    string version = Environment.Version.Major < 4 ? "2.0" : "4.0";
                    program = string.Format( "postsharp.{0}-{1}.exe", version, processor );
                         
                }


                string commandLine = Path.Combine( Environment.ExpandEnvironmentVariables( postsharpPath.Replace( "~", applicationPath ) ),
                                                   program );
                if ( !File.Exists( commandLine ) )
                {
                    throw new ConfigurationErrorsException(
                        string.Format( "Cannot find the file: '{0}'.", commandLine ) );
                }

                // We create our directories there and take a copy of the input assembly.
                string shadowDirectory = Path.Combine( Path.GetDirectoryName( path ), "before-postsharp" );
                if ( !Directory.Exists( shadowDirectory ) )
                    Directory.CreateDirectory( shadowDirectory );
                string shadowAssembly = Path.Combine( shadowDirectory, Path.GetFileName( path ) );
                File.Copy( path, shadowAssembly, true );
                string pdbFile = Path.ChangeExtension( path, ".pdb" );
                string shadowPdb = Path.Combine( shadowDirectory, Path.GetFileName( pdbFile ) );
                if ( File.Exists( pdbFile ) )
                    File.Copy( pdbFile, shadowPdb, true );
                string intermediateDirectory = Path.Combine( Path.GetDirectoryName( path ), "postsharp" );
                if ( !Directory.Exists( intermediateDirectory ) )
                    Directory.CreateDirectory( intermediateDirectory );
                string outputAssembly = Path.Combine( intermediateDirectory, Path.GetFileName( path ) );
                string outputPdb = Path.Combine( intermediateDirectory, Path.GetFileName( pdbFile ) );

                Process process = new Process
                                      {
                                          StartInfo =
                                              {
                                                  FileName = commandLine,
                                                  ErrorDialog = false,
                                                  CreateNoWindow = true,
                                                  UseShellExecute = false,
                                                  RedirectStandardError = true,
                                                  RedirectStandardOutput = true
                                              }
                                      };


                StringBuilder arguments = new StringBuilder();
                arguments.AppendFormat(
                    "\"/P:Output={0}\" \"/P:IntermediateDirectory={1} \"  /P:CleanIntermediate=False /P:SignAssembly=False /P:SearchPath=\"",
                    outputAssembly, intermediateDirectory );

                foreach ( NameValueConfigurationElement searchPath in configuration.SearchPath )
                {
                    string value = Environment.ExpandEnvironmentVariables( searchPath.Value.Replace( "~", applicationPath ) );


                    arguments.Append( value );
                    arguments.Append( "," );
                }

                arguments.Append( "\" " );

                foreach ( NameValueConfigurationElement parameter in configuration.PostSharpProperties )
                {
                    arguments.Append("/P:\"");
                    arguments.Append( parameter.Name );
                    arguments.Append( '=' );
                    arguments.Append( parameter.Value.Replace( "~", applicationPath ) );
                    arguments.Append( "\" " );
                }

                if ( configuration.AttachDebugger )
                {
                    arguments.Append( "/Attach " );
                }

                bool traceEnabled = !string.IsNullOrEmpty( configuration.TraceCategories );
                if ( traceEnabled )
                {
                    arguments.Append( "/V " );

                    foreach ( string category in configuration.TraceCategories.Split( ',', ';', ' ') )
                    {
                        arguments.AppendFormat( "/T:{0} ", category );
                    }
                }

                arguments.Append("/NoUpdateCheck \"");
                arguments.Append( project );
                arguments.Append( "\" \"" );
                arguments.Append( shadowAssembly );
                arguments.Append( '\"' );

                process.StartInfo.Arguments = arguments.ToString();

                LogWriter logWriter;
                string logMessage;
                if ( traceEnabled )
                {
                    string logFile = Path.Combine( Path.GetTempPath(),
                                                   string.Format( "postsharp-aspnet-{0:yyyy-MM-dd}.log", DateTime.Now ) );
                    logMessage = string.Format( " See file '{0}' for details.", logFile );
                    logWriter = new LogWriter( logFile );
                    logWriter.Writer.WriteLine( "--------------------------------------------------------------" );
                    logWriter.Writer.WriteLine( process.StartInfo.FileName + " " + process.StartInfo.Arguments );
                    process.ErrorDataReceived += logWriter.OnPostSharpDataReceived;
                    process.OutputDataReceived += logWriter.OnPostSharpDataReceived;
                }
                else
                {
                    logWriter = null;
                    logMessage = " Enable PostSharp tracing to see details.";
                }

                process.ErrorDataReceived += (( sender, e ) => Console.WriteLine( e.Data ));
                process.OutputDataReceived += (( sender, e ) => Console.WriteLine( e.Data ));

                Console.WriteLine( "Starting: {0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments );
                process.Start();

                process.BeginErrorReadLine();
                process.BeginOutputReadLine();

                bool success = process.WaitForExit( 60000 );

                if ( logWriter != null )
                    logWriter.Dispose();

                if ( !success )
                    throw new ApplicationException( "PostSharp did not finish in due time." + logMessage );

                if ( process.ExitCode != 0 )
                    throw new ApplicationException( "PostSharp did not complete successfully." + logMessage );

                File.Copy( outputAssembly, path, true );
                if ( File.Exists( outputPdb ) )
                    File.Copy( outputPdb, pdbFile, true );
            }
            catch ( Exception e )
            {
                Console.WriteLine( e.ToString() );
                throw;
            }
        }

        /// <summary>
        /// Disposes the current object.
        /// </summary>
        public void Dispose()
        {
        }

        private class LogWriter : IDisposable
        {
            private StreamWriter writer;

            public LogWriter( string fileName )
            {
                writer = new StreamWriter( fileName, true, Encoding.UTF8 );
            }

            public StreamWriter Writer
            {
                get { return writer; }
            }


            public void OnPostSharpDataReceived( object sender, DataReceivedEventArgs e )
            {
                if ( writer != null )
                {
                    writer.WriteLine( e.Data );
                }
            }

            public void Dispose()
            {
                if ( writer != null )
                {
                    writer.Flush();
                    writer.Dispose();
                    writer = null;
                }
            }
        }
    }
}